// flowchart, v1.4.1
// Copyright (c)2015 Adriano Raiano (adrai).
// Distributed under MIT license
// http://adrai.github.io/flowchart.js
(function () {

    // add indexOf to non ECMA-262 standard compliant browsers
    if (!Array.prototype.indexOf) {
        Array.prototype.indexOf = function (searchElement /*, fromIndex */) {
            "use strict";
            if (this === null) {
                throw new TypeError();
            }
            var t = Object(this);
            var len = t.length >>> 0;
            if (len === 0) {
                return -1;
            }
            var n = 0;
            if (arguments.length > 0) {
                n = Number(arguments[1]);
                if (n != n) { // shortcut for verifying if it's NaN
                    n = 0;
                } else if (n !== 0 && n != Infinity && n != -Infinity) {
                    n = (n > 0 || -1) * Math.floor(Math.abs(n));
                }
            }
            if (n >= len) {
                return -1;
            }
            var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
            for (; k < len; k++) {
                if (k in t && t[k] === searchElement) {
                    return k;
                }
            }
            return -1;
        };
    }

    // add lastIndexOf to non ECMA-262 standard compliant browsers
    if (!Array.prototype.lastIndexOf) {
        Array.prototype.lastIndexOf = function (searchElement /*, fromIndex*/) {
            "use strict";
            if (this === null) {
                throw new TypeError();
            }
            var t = Object(this);
            var len = t.length >>> 0;
            if (len === 0) {
                return -1;
            }
            var n = len;
            if (arguments.length > 1) {
                n = Number(arguments[1]);
                if (n != n) {
                    n = 0;
                } else if (n !== 0 && n != (1 / 0) && n != -(1 / 0)) {
                    n = (n > 0 || -1) * Math.floor(Math.abs(n));
                }
            }
            var k = n >= 0 ? Math.min(n, len - 1) : len - Math.abs(n);
            for (; k >= 0; k--) {
                if (k in t && t[k] === searchElement) {
                    return k;
                }
            }
            return -1;
        };
    }

    if (!String.prototype.trim) {
        String.prototype.trim = function () {
            return this.replace(/^\s+|\s+$/g, '');
        };
    }

    var root = this,
        flowchart = {};

    // Export the flowchart object for **CommonJS**.
    // If we're not in CommonJS, add `flowchart` to the
    // global object or to jquery.
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = flowchart;
    } else {
        root.flowchart = root.flowchart || flowchart;
    }
    // defaults
    var o = {
        'x': 0,
        'y': 0,
        'line-width': 3,
        'line-length': 50,
        'text-margin': 10,
        'font-size': 14,
        'font-color': 'black',
        // 'font': 'normal',
        // 'font-family': 'calibri',
        // 'font-weight': 'normal',
        'line-color': 'black',
        'element-color': 'black',
        'fill': 'white',
        'yes-text': 'yes',
        'no-text': 'no',
        'arrow-end': 'block',
        'class': 'flowchart',
        'scale': 1,
        'symbols': {
            'start': {},
            'end': {},
            'condition': {},
            'inputoutput': {},
            'operation': {},
            'subroutine': {}
        }//,
        // 'flowstate' : {
        //   'past' : { 'fill': '#CCCCCC', 'font-size': 12},
        //   'current' : {'fill': 'yellow', 'font-color': 'red', 'font-weight': 'bold'},
        //   'future' : { 'fill': '#FFFF99'},
        //   'invalid': {'fill': '#444444'}
        // }
    };

    function _defaults(options, defaultOptions) {
        if (!options || typeof options === 'function') {
            return defaultOptions;
        }

        var merged = {};
        for (var attrname in defaultOptions) {
            merged[attrname] = defaultOptions[attrname];
        }

        for (attrname in options) {
            if (options[attrname]) {
                if (typeof merged[attrname] === 'object') {
                    merged[attrname] = _defaults(merged[attrname], options[attrname]);
                } else {
                    merged[attrname] = options[attrname];
                }
            }
        }
        return merged;
    }

    function _inherits(ctor, superCtor) {
        if (typeof(Object.create) === 'function') {
            // implementation from standard node.js 'util' module
            ctor.super_ = superCtor;
            ctor.prototype = Object.create(superCtor.prototype, {
                constructor: {
                    value: ctor,
                    enumerable: false,
                    writable: true,
                    configurable: true
                }
            });
        } else {
            // old school shim for old browsers
            ctor.super_ = superCtor;
            var TempCtor = function () {
            };
            TempCtor.prototype = superCtor.prototype;
            ctor.prototype = new TempCtor();
            ctor.prototype.constructor = ctor;
        }
    }

    // move dependent functions to a container so that
    // they can be overriden easier in no jquery environment (node.js)
    var f = {
        defaults: _defaults,
        inherits: _inherits
    };

    function drawPath(chart, location, points) {
        var i, len;
        var path = 'M{0},{1}';
        for (i = 2, len = 2 * points.length + 2; i < len; i += 2) {
            path += ' L{' + i + '},{' + (i + 1) + '}';
        }
        var pathValues = [location.x, location.y];
        for (i = 0, len = points.length; i < len; i++) {
            pathValues.push(points[i].x);
            pathValues.push(points[i].y);
        }
        var symbol = chart.paper.path(path, pathValues);
        symbol.attr('stroke', chart.options['element-color']);
        symbol.attr('stroke-width', chart.options['line-width']);

        var font = chart.options['font'];
        var fontF = chart.options['font-family'];
        var fontW = chart.options['font-weight'];

        if (font) symbol.attr({'font': font});
        if (fontF) symbol.attr({'font-family': fontF});
        if (fontW) symbol.attr({'font-weight': fontW});

        return symbol;
    }

    function drawLine(chart, from, to, text) {
        var i, len;

        if (Object.prototype.toString.call(to) !== '[object Array]') {
            to = [to];
        }

        var path = 'M{0},{1}';
        for (i = 2, len = 2 * to.length + 2; i < len; i += 2) {
            path += ' L{' + i + '},{' + (i + 1) + '}';
        }
        var pathValues = [from.x, from.y];
        for (i = 0, len = to.length; i < len; i++) {
            pathValues.push(to[i].x);
            pathValues.push(to[i].y);
        }

        var line = chart.paper.path(path, pathValues);
        line.attr({
            stroke: chart.options['line-color'],
            'stroke-width': chart.options['line-width'],
            'arrow-end': chart.options['arrow-end']
        });

        var font = chart.options['font'];
        var fontF = chart.options['font-family'];
        var fontW = chart.options['font-weight'];

        if (font) line.attr({'font': font});
        if (fontF) line.attr({'font-family': fontF});
        if (fontW) line.attr({'font-weight': fontW});

        if (text) {

            var centerText = false;

            var textPath = chart.paper.text(0, 0, text);

            var isHorizontal = false;
            var firstTo = to[0];

            if (from.y === firstTo.y) {
                isHorizontal = true;
            }

            var x = 0,
                y = 0;

            if (centerText) {
                if (from.x > firstTo.x) {
                    x = from.x - (from.x - firstTo.x) / 2;
                } else {
                    x = firstTo.x - (firstTo.x - from.x) / 2;
                }

                if (from.y > firstTo.y) {
                    y = from.y - (from.y - firstTo.y) / 2;
                } else {
                    y = firstTo.y - (firstTo.y - from.y) / 2;
                }

                if (isHorizontal) {
                    x -= textPath.getBBox().width / 2;
                    y -= chart.options['text-margin'];
                } else {
                    x += chart.options['text-margin'];
                    y -= textPath.getBBox().height / 2;
                }
            } else {
                x = from.x;
                y = from.y;

                if (isHorizontal) {
                    x += chart.options['text-margin'] / 2;
                    y -= chart.options['text-margin'];
                } else {
                    x += chart.options['text-margin'] / 2;
                    y += chart.options['text-margin'];
                }
            }

            textPath.attr({
                'text-anchor': 'start',
                'font-size': chart.options['font-size'],
                'fill': chart.options['font-color'],
                x: x,
                y: y
            });

            if (font) textPath.attr({'font': font});
            if (fontF) textPath.attr({'font-family': fontF});
            if (fontW) textPath.attr({'font-weight': fontW});
        }

        return line;
    }

    function checkLineIntersection(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
        // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point
        var denominator, a, b, numerator1, numerator2, result = {
            x: null,
            y: null,
            onLine1: false,
            onLine2: false
        };
        denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
        if (denominator === 0) {
            return result;
        }
        a = line1StartY - line2StartY;
        b = line1StartX - line2StartX;
        numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
        numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
        a = numerator1 / denominator;
        b = numerator2 / denominator;

        // if we cast these lines infinitely in both directions, they intersect here:
        result.x = line1StartX + (a * (line1EndX - line1StartX));
        result.y = line1StartY + (a * (line1EndY - line1StartY));
        /*
         // it is worth noting that this should be the same as:
         x = line2StartX + (b * (line2EndX - line2StartX));
         y = line2StartX + (b * (line2EndY - line2StartY));
         */
        // if line1 is a segment and line2 is infinite, they intersect if:
        if (a > 0 && a < 1) {
            result.onLine1 = true;
        }
        // if line2 is a segment and line1 is infinite, they intersect if:
        if (b > 0 && b < 1) {
            result.onLine2 = true;
        }
        // if line1 and line2 are segments, they intersect if both of the above are true
        return result;
    }

    function FlowChart(container, options) {
        options = options || {};

        this.paper = new Raphael(container);

        this.options = f.defaults(options, o);

        this.symbols = [];
        this.lines = [];
        this.start = null;
    }

    FlowChart.prototype.handle = function (symbol) {
        if (this.symbols.indexOf(symbol) <= -1) {
            this.symbols.push(symbol);
        }

        var flowChart = this;

        if (symbol instanceof (Condition)) {
            symbol.yes = function (nextSymbol) {
                symbol.yes_symbol = nextSymbol;
                if (symbol.no_symbol) {
                    symbol.pathOk = true;
                }
                return flowChart.handle(nextSymbol);
            };
            symbol.no = function (nextSymbol) {
                symbol.no_symbol = nextSymbol;
                if (symbol.yes_symbol) {
                    symbol.pathOk = true;
                }
                return flowChart.handle(nextSymbol);
            };
        } else {
            symbol.then = function (nextSymbol) {
                symbol.next = nextSymbol;
                symbol.pathOk = true;
                return flowChart.handle(nextSymbol);
            };
        }

        return symbol;
    };

    FlowChart.prototype.startWith = function (symbol) {
        this.start = symbol;
        return this.handle(symbol);
    };

    FlowChart.prototype.render = function () {
        var maxWidth = 0,
            maxHeight = 0,
            i = 0,
            len = 0,
            maxX = 0,
            maxY = 0,
            symbol;

        for (i = 0, len = this.symbols.length; i < len; i++) {
            symbol = this.symbols[i];
            if (symbol.width > maxWidth) {
                maxWidth = symbol.width;
            }
            if (symbol.height > maxHeight) {
                maxHeight = symbol.height;
            }
        }

        for (i = 0, len = this.symbols.length; i < len; i++) {
            symbol = this.symbols[i];
            symbol.shiftX(this.options.x + (maxWidth - symbol.width) / 2 + this.options['line-width']);
            symbol.shiftY(this.options.y + (maxHeight - symbol.height) / 2 + this.options['line-width']);
        }

        this.start.render();
        // for (i = 0, len = this.symbols.length; i < len; i++) {
        //   symbol = this.symbols[i];
        //   symbol.render();
        // }

        for (i = 0, len = this.symbols.length; i < len; i++) {
            symbol = this.symbols[i];
            symbol.renderLines();
        }

        maxX = this.maxXFromLine;

        for (i = 0, len = this.symbols.length; i < len; i++) {
            symbol = this.symbols[i];
            var x = symbol.getX() + symbol.width;
            var y = symbol.getY() + symbol.height;
            if (x > maxX) {
                maxX = x;
            }
            if (y > maxY) {
                maxY = y;
            }
        }

        var scale = this.options['scale'];
        var lineWidth = this.options['line-width'];
        this.paper.setSize((maxX * scale) + (lineWidth * scale), (maxY * scale) + (lineWidth * scale));
        this.paper.setViewBox(0, 0, maxX + lineWidth, maxY + lineWidth, true);
    };

    FlowChart.prototype.clean = function () {
        if (this.paper) {
            var paperDom = this.paper.canvas;
            paperDom.parentNode.removeChild(paperDom);
        }
    };
    function Symbol(chart, options, symbol) {
        this.chart = chart;
        this.group = this.chart.paper.set();
        this.symbol = symbol;
        this.connectedTo = [];
        this.symbolType = options.symbolType;
        this.flowstate = (options.flowstate || 'future');

        this.next_direction = options.next && options['direction_next'] ? options['direction_next'] : undefined;

        this.text = this.chart.paper.text(0, 0, options.text);
        //Raphael does not support the svg group tag so setting the text node id to the symbol node id plus t
        if (options.key) {
            this.text.node.id = options.key + 't';
        }
        this.text.node.setAttribute('class', this.getAttr('class') + 't');

        this.text.attr({
            'text-anchor': 'start',
            'x': this.getAttr('text-margin'),
            'fill': this.getAttr('font-color'),
            'font-size': this.getAttr('font-size')
        });

        var font = this.getAttr('font');
        var fontF = this.getAttr('font-family');
        var fontW = this.getAttr('font-weight');

        if (font) this.text.attr({'font': font});
        if (fontF) this.text.attr({'font-family': fontF});
        if (fontW) this.text.attr({'font-weight': fontW});

        if (options.link) {
            this.text.attr('href', options.link);
        }
        if (options.target) {
            this.text.attr('target', options.target);
        }

        var maxWidth = this.getAttr('maxWidth');
        if (maxWidth) {
            // using this approach: http://stackoverflow.com/a/3153457/22466
            var words = options.text.split(' ');
            var tempText = "";
            for (var i = 0, ii = words.length; i < ii; i++) {
                var word = words[i];
                this.text.attr("text", tempText + " " + word);
                if (this.text.getBBox().width > maxWidth) {
                    tempText += "\n" + word;
                } else {
                    tempText += " " + word;
                }
            }
            this.text.attr("text", tempText.substring(1));
        }

        this.group.push(this.text);

        if (symbol) {
            var tmpMargin = this.getAttr('text-margin');

            symbol.attr({
                'fill': this.getAttr('fill'),
                'stroke': this.getAttr('element-color'),
                'stroke-width': this.getAttr('line-width'),
                'width': this.text.getBBox().width + 2 * tmpMargin,
                'height': this.text.getBBox().height + 2 * tmpMargin
            });

            symbol.node.setAttribute('class', this.getAttr('class'));

            if (options.link) {
                symbol.attr('href', options.link);
            }
            if (options.target) {
                symbol.attr('target', options.target);
            }
            if (options.key) {
                symbol.node.id = options.key;
            }

            this.group.push(symbol);
            symbol.insertBefore(this.text);

            this.text.attr({
                'y': symbol.getBBox().height / 2
            });

            this.initialize();
        }

    }

    /* Gets the attribute based on Flowstate, Symbol-Name and default, first found wins */
    Symbol.prototype.getAttr = function (attName) {
        if (!this.chart) {
            return undefined;
        }
        var opt3 = (this.chart.options) ? this.chart.options[attName] : undefined;
        var opt2 = (this.chart.options.symbols) ? this.chart.options.symbols[this.symbolType][attName] : undefined;
        var opt1;
        if (this.chart.options.flowstate && this.chart.options.flowstate[this.flowstate]) {
            opt1 = this.chart.options.flowstate[this.flowstate][attName];
        }
        return (opt1 || opt2 || opt3);
    };

    Symbol.prototype.initialize = function () {
        this.group.transform('t' + this.getAttr('line-width') + ',' + this.getAttr('line-width'));

        this.width = this.group.getBBox().width;
        this.height = this.group.getBBox().height;
    };

    Symbol.prototype.getCenter = function () {
        return {
            x: this.getX() + this.width / 2,
            y: this.getY() + this.height / 2
        };
    };

    Symbol.prototype.getX = function () {
        return this.group.getBBox().x;
    };

    Symbol.prototype.getY = function () {
        return this.group.getBBox().y;
    };

    Symbol.prototype.shiftX = function (x) {
        this.group.transform('t' + (this.getX() + x) + ',' + this.getY());
    };

    Symbol.prototype.setX = function (x) {
        this.group.transform('t' + x + ',' + this.getY());
    };

    Symbol.prototype.shiftY = function (y) {
        this.group.transform('t' + this.getX() + ',' + (this.getY() + y));
    };

    Symbol.prototype.setY = function (y) {
        this.group.transform('t' + this.getX() + ',' + y);
    };

    Symbol.prototype.getTop = function () {
        var y = this.getY();
        var x = this.getX() + this.width / 2;
        return {x: x, y: y};
    };

    Symbol.prototype.getBottom = function () {
        var y = this.getY() + this.height;
        var x = this.getX() + this.width / 2;
        return {x: x, y: y};
    };

    Symbol.prototype.getLeft = function () {
        var y = this.getY() + this.group.getBBox().height / 2;
        var x = this.getX();
        return {x: x, y: y};
    };

    Symbol.prototype.getRight = function () {
        var y = this.getY() + this.group.getBBox().height / 2;
        var x = this.getX() + this.group.getBBox().width;
        return {x: x, y: y};
    };

    Symbol.prototype.render = function () {
        if (this.next) {

            var lineLength = this.getAttr('line-length');

            if (this.next_direction === 'right') {

                var rightPoint = this.getRight();
                var leftPoint = this.next.getLeft();

                if (!this.next.isPositioned) {

                    this.next.setY(rightPoint.y - this.next.height / 2);
                    this.next.shiftX(this.group.getBBox().x + this.width + lineLength);

                    var self = this;
                    (function shift() {
                        var hasSymbolUnder = false;
                        var symb;
                        for (var i = 0, len = self.chart.symbols.length; i < len; i++) {
                            symb = self.chart.symbols[i];

                            var diff = Math.abs(symb.getCenter().x - self.next.getCenter().x);
                            if (symb.getCenter().y > self.next.getCenter().y && diff <= self.next.width / 2) {
                                hasSymbolUnder = true;
                                break;
                            }
                        }

                        if (hasSymbolUnder) {
                            self.next.setX(symb.getX() + symb.width + lineLength);
                            shift();
                        }
                    })();

                    this.next.isPositioned = true;

                    this.next.render();
                }
            } else {
                var bottomPoint = this.getBottom();
                var topPoint = this.next.getTop();

                if (!this.next.isPositioned) {
                    this.next.shiftY(this.getY() + this.height + lineLength);
                    this.next.setX(bottomPoint.x - this.next.width / 2);
                    this.next.isPositioned = true;

                    this.next.render();
                }
            }
        }
    };

    Symbol.prototype.renderLines = function () {
        if (this.next) {
            if (this.next_direction) {
                this.drawLineTo(this.next, '', this.next_direction);
            } else {
                this.drawLineTo(this.next);
            }
        }
    };

    Symbol.prototype.drawLineTo = function (symbol, text, origin) {
        if (this.connectedTo.indexOf(symbol) < 0) {
            this.connectedTo.push(symbol);
        }

        var x = this.getCenter().x,
            y = this.getCenter().y,
            top = this.getTop(),
            right = this.getRight(),
            bottom = this.getBottom(),
            left = this.getLeft();

        var symbolX = symbol.getCenter().x,
            symbolY = symbol.getCenter().y,
            symbolTop = symbol.getTop(),
            symbolRight = symbol.getRight(),
            symbolBottom = symbol.getBottom(),
            symbolLeft = symbol.getLeft();

        var isOnSameColumn = x === symbolX,
            isOnSameLine = y === symbolY,
            isUnder = y < symbolY,
            isUpper = y > symbolY,
            isLeft = x > symbolX,
            isRight = x < symbolX;

        var maxX = 0,
            line,
            lineLength = this.getAttr('line-length'),
            lineWith = this.getAttr('line-width');

        if ((!origin || origin === 'bottom') && isOnSameColumn && isUnder) {
            line = drawLine(this.chart, bottom, symbolTop, text);
            this.bottomStart = true;
            symbol.topEnd = true;
            maxX = bottom.x;
        } else if ((!origin || origin === 'right') && isOnSameLine && isRight) {
            line = drawLine(this.chart, right, symbolLeft, text);
            this.rightStart = true;
            symbol.leftEnd = true;
            maxX = symbolLeft.x;
        } else if ((!origin || origin === 'left') && isOnSameLine && isLeft) {
            line = drawLine(this.chart, left, symbolRight, text);
            this.leftStart = true;
            symbol.rightEnd = true;
            maxX = symbolRight.x;
        } else if ((!origin || origin === 'right') && isOnSameColumn && isUpper) {
            line = drawLine(this.chart, right, [
                {x: right.x + lineLength / 2, y: right.y},
                {x: right.x + lineLength / 2, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y}
            ], text);
            this.rightStart = true;
            symbol.topEnd = true;
            maxX = right.x + lineLength / 2;
        } else if ((!origin || origin === 'right') && isOnSameColumn && isUnder) {
            line = drawLine(this.chart, right, [
                {x: right.x + lineLength / 2, y: right.y},
                {x: right.x + lineLength / 2, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y}
            ], text);
            this.rightStart = true;
            symbol.topEnd = true;
            maxX = right.x + lineLength / 2;
        } else if ((!origin || origin === 'bottom') && isLeft) {
            if (this.leftEnd && isUpper) {
                line = drawLine(this.chart, bottom, [
                    {x: bottom.x, y: bottom.y + lineLength / 2},
                    {x: bottom.x + (bottom.x - symbolTop.x) / 2, y: bottom.y + lineLength / 2},
                    {x: bottom.x + (bottom.x - symbolTop.x) / 2, y: symbolTop.y - lineLength / 2},
                    {x: symbolTop.x, y: symbolTop.y - lineLength / 2},
                    {x: symbolTop.x, y: symbolTop.y}
                ], text);
            } else {
                line = drawLine(this.chart, bottom, [
                    {x: bottom.x, y: symbolTop.y - lineLength / 2},
                    {x: symbolTop.x, y: symbolTop.y - lineLength / 2},
                    {x: symbolTop.x, y: symbolTop.y}
                ], text);
            }
            this.bottomStart = true;
            symbol.topEnd = true;
            maxX = bottom.x + (bottom.x - symbolTop.x) / 2;
        } else if ((!origin || origin === 'bottom') && isRight) {
            line = drawLine(this.chart, bottom, [
                {x: bottom.x, y: bottom.y + lineLength / 2},
                {x: bottom.x + (bottom.x - symbolTop.x) / 2, y: bottom.y + lineLength / 2},
                {x: bottom.x + (bottom.x - symbolTop.x) / 2, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y}
            ], text);
            this.bottomStart = true;
            symbol.topEnd = true;
            maxX = bottom.x + (bottom.x - symbolTop.x) / 2;
        } else if ((origin && origin === 'right') && isLeft) {
            line = drawLine(this.chart, right, [
                {x: right.x + lineLength / 2, y: right.y},
                {x: right.x + lineLength / 2, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y}
            ], text);
            this.rightStart = true;
            symbol.topEnd = true;
            maxX = right.x + lineLength / 2;
        } else if ((origin && origin === 'right') && isRight) {
            line = drawLine(this.chart, right, [
                {x: symbolTop.x, y: right.y},
                {x: symbolTop.x, y: symbolTop.y}
            ], text);
            this.rightStart = true;
            symbol.topEnd = true;
            maxX = right.x + lineLength / 2;
        } else if ((origin && origin === 'bottom') && isOnSameColumn && isUpper) {
            line = drawLine(this.chart, bottom, [
                {x: bottom.x, y: bottom.y + lineLength / 2},
                {x: right.x + lineLength / 2, y: bottom.y + lineLength / 2},
                {x: right.x + lineLength / 2, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y}
            ], text);
            this.bottomStart = true;
            symbol.topEnd = true;
            maxX = bottom.x + lineLength / 2;
        } else if ((origin === 'left') && isOnSameColumn && isUpper) {
            var diffX = left.x - lineLength / 2;
            if (symbolLeft.x < left.x) {
                diffX = symbolLeft.x - lineLength / 2;
            }
            line = drawLine(this.chart, left, [
                {x: diffX, y: left.y},
                {x: diffX, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y}
            ], text);
            this.leftStart = true;
            symbol.topEnd = true;
            maxX = left.x;
        } else if ((origin === 'left')) {
            line = drawLine(this.chart, left, [
                {x: symbolTop.x + (left.x - symbolTop.x) / 2, y: left.y},
                {x: symbolTop.x + (left.x - symbolTop.x) / 2, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y - lineLength / 2},
                {x: symbolTop.x, y: symbolTop.y}
            ], text);
            this.leftStart = true;
            symbol.topEnd = true;
            maxX = left.x;
        }

        if (line) {
            var self = this;
            for (var l = 0, llen = this.chart.lines.length; l < llen; l++) {
                var otherLine = this.chart.lines[l];
                var i,
                    len,
                    intersections,
                    inter;

                var ePath = otherLine.attr('path'),
                    lPath = line.attr('path');

                for (var iP = 0, lenP = ePath.length - 1; iP < lenP; iP++) {
                    var newPath = [];
                    newPath.push(['M', ePath[iP][1], ePath[iP][2]]);
                    newPath.push(['L', ePath[iP + 1][1], ePath[iP + 1][2]]);

                    var line1_from_x = newPath[0][1];
                    var line1_from_y = newPath[0][2];
                    var line1_to_x = newPath[1][1];
                    var line1_to_y = newPath[1][2];

                    for (var lP = 0, lenlP = lPath.length - 1; lP < lenlP; lP++) {
                        var newLinePath = [];
                        newLinePath.push(['M', lPath[lP][1], lPath[lP][2]]);
                        newLinePath.push(['L', lPath[lP + 1][1], lPath[lP + 1][2]]);

                        var line2_from_x = newLinePath[0][1];
                        var line2_from_y = newLinePath[0][2];
                        var line2_to_x = newLinePath[1][1];
                        var line2_to_y = newLinePath[1][2];

                        var res = checkLineIntersection(line1_from_x, line1_from_y, line1_to_x, line1_to_y, line2_from_x, line2_from_y, line2_to_x, line2_to_y);
                        if (res.onLine1 && res.onLine2) {

                            var newSegment;
                            if (line2_from_y === line2_to_y) {
                                if (line2_from_x > line2_to_x) {
                                    newSegment = ['L', res.x + lineWith * 2, line2_from_y];
                                    lPath.splice(lP + 1, 0, newSegment);
                                    newSegment = ['C', res.x + lineWith * 2, line2_from_y, res.x, line2_from_y - lineWith * 4, res.x - lineWith * 2, line2_from_y];
                                    lPath.splice(lP + 2, 0, newSegment);
                                    line.attr('path', lPath);
                                } else {
                                    newSegment = ['L', res.x - lineWith * 2, line2_from_y];
                                    lPath.splice(lP + 1, 0, newSegment);
                                    newSegment = ['C', res.x - lineWith * 2, line2_from_y, res.x, line2_from_y - lineWith * 4, res.x + lineWith * 2, line2_from_y];
                                    lPath.splice(lP + 2, 0, newSegment);
                                    line.attr('path', lPath);
                                }
                            } else {
                                if (line2_from_y > line2_to_y) {
                                    newSegment = ['L', line2_from_x, res.y + lineWith * 2];
                                    lPath.splice(lP + 1, 0, newSegment);
                                    newSegment = ['C', line2_from_x, res.y + lineWith * 2, line2_from_x + lineWith * 4, res.y, line2_from_x, res.y - lineWith * 2];
                                    lPath.splice(lP + 2, 0, newSegment);
                                    line.attr('path', lPath);
                                } else {
                                    newSegment = ['L', line2_from_x, res.y - lineWith * 2];
                                    lPath.splice(lP + 1, 0, newSegment);
                                    newSegment = ['C', line2_from_x, res.y - lineWith * 2, line2_from_x + lineWith * 4, res.y, line2_from_x, res.y + lineWith * 2];
                                    lPath.splice(lP + 2, 0, newSegment);
                                    line.attr('path', lPath);
                                }
                            }

                            lP += 2;
                            len += 2;
                        }
                    }
                }
            }

            this.chart.lines.push(line);
        }

        if (!this.chart.maxXFromLine || (this.chart.maxXFromLine && maxX > this.chart.maxXFromLine)) {
            this.chart.maxXFromLine = maxX;
        }
    };
    function Start(chart, options) {
        var symbol = chart.paper.rect(0, 0, 0, 0, 20);
        options = options || {};
        options.text = options.text || 'Start';
        Symbol.call(this, chart, options, symbol);
    }

    f.inherits(Start, Symbol);


    // Start.prototype.render = function() {
    //   if (this.next) {
    //     var lineLength = this.chart.options.symbols[this.symbolType]['line-length'] || this.chart.options['line-length'];

    //     var bottomPoint = this.getBottom();
    //     var topPoint = this.next.getTop();

    //     if (!this.next.isPositioned) {
    //       this.next.shiftY(this.getY() + this.height + lineLength);
    //       this.next.setX(bottomPoint.x - this.next.width/2);
    //       this.next.isPositioned = true;

    //       this.next.render();
    //     }
    //   }
    // };

    // Start.prototype.renderLines = function() {
    //   if (this.next) {
    //     this.drawLineTo(this.next);
    //   }
    // };
    function End(chart, options) {
        var symbol = chart.paper.rect(0, 0, 0, 0, 20);
        options = options || {};
        options.text = options.text || 'End';
        Symbol.call(this, chart, options, symbol);
    }

    f.inherits(End, Symbol);
    function Operation(chart, options) {
        var symbol = chart.paper.rect(0, 0, 0, 0);
        options = options || {};
        Symbol.call(this, chart, options, symbol);
    }

    f.inherits(Operation, Symbol);
    function Subroutine(chart, options) {
        var symbol = chart.paper.rect(0, 0, 0, 0);
        options = options || {};
        Symbol.call(this, chart, options, symbol);

        symbol.attr({
            width: this.text.getBBox().width + 4 * this.getAttr('text-margin')
        });

        this.text.attr({
            'x': 2 * this.getAttr('text-margin')
        });

        var innerWrap = chart.paper.rect(0, 0, 0, 0);
        innerWrap.attr({
            x: this.getAttr('text-margin'),
            stroke: this.getAttr('element-color'),
            'stroke-width': this.getAttr('line-width'),
            width: this.text.getBBox().width + 2 * this.getAttr('text-margin'),
            height: this.text.getBBox().height + 2 * this.getAttr('text-margin'),
            fill: this.getAttr('fill')
        });
        if (options.key) {
            innerWrap.node.id = options.key + 'i';
        }

        var font = this.getAttr('font');
        var fontF = this.getAttr('font-family');
        var fontW = this.getAttr('font-weight');

        if (font) innerWrap.attr({'font': font});
        if (fontF) innerWrap.attr({'font-family': fontF});
        if (fontW) innerWrap.attr({'font-weight': fontW});

        if (options.link) {
            innerWrap.attr('href', options.link);
        }
        if (options.target) {
            innerWrap.attr('target', options.target);
        }
        this.group.push(innerWrap);
        innerWrap.insertBefore(this.text);

        this.initialize();
    }

    f.inherits(Subroutine, Symbol);
    function InputOutput(chart, options) {
        options = options || {};
        Symbol.call(this, chart, options);
        this.textMargin = this.getAttr('text-margin');

        this.text.attr({
            x: this.textMargin * 3
        });

        var width = this.text.getBBox().width + 4 * this.textMargin;
        var height = this.text.getBBox().height + 2 * this.textMargin;
        var startX = this.textMargin;
        var startY = height / 2;

        var start = {x: startX, y: startY};
        var points = [
            {x: startX - this.textMargin, y: height},
            {x: startX - this.textMargin + width, y: height},
            {x: startX - this.textMargin + width + 2 * this.textMargin, y: 0},
            {x: startX - this.textMargin + 2 * this.textMargin, y: 0},
            {x: startX, y: startY}
        ];

        var symbol = drawPath(chart, start, points);

        symbol.attr({
            stroke: this.getAttr('element-color'),
            'stroke-width': this.getAttr('line-width'),
            fill: this.getAttr('fill')
        });
        if (options.link) {
            symbol.attr('href', options.link);
        }
        if (options.target) {
            symbol.attr('target', options.target);
        }
        if (options.key) {
            symbol.node.id = options.key;
        }
        symbol.node.setAttribute('class', this.getAttr('class'));

        this.text.attr({
            y: symbol.getBBox().height / 2
        });

        this.group.push(symbol);
        symbol.insertBefore(this.text);

        this.initialize();
    }

    f.inherits(InputOutput, Symbol);

    InputOutput.prototype.getLeft = function () {
        var y = this.getY() + this.group.getBBox().height / 2;
        var x = this.getX() + this.textMargin;
        return {x: x, y: y};
    };

    InputOutput.prototype.getRight = function () {
        var y = this.getY() + this.group.getBBox().height / 2;
        var x = this.getX() + this.group.getBBox().width - this.textMargin;
        return {x: x, y: y};
    };
    function Condition(chart, options) {
        options = options || {};
        Symbol.call(this, chart, options);
        this.textMargin = this.getAttr('text-margin');
        this.yes_direction = 'bottom';
        this.no_direction = 'right';
        if (options.yes && options['direction_yes'] && options.no && !options['direction_no']) {
            if (options['direction_yes'] === 'right') {
                this.no_direction = 'bottom';
                this.yes_direction = 'right';
            } else {
                this.no_direction = 'right';
                this.yes_direction = 'bottom';
            }
        } else if (options.yes && !options['direction_yes'] && options.no && options['direction_no']) {
            if (options['direction_no'] === 'right') {
                this.yes_direction = 'bottom';
                this.no_direction = 'right';
            } else {
                this.yes_direction = 'right';
                this.no_direction = 'bottom';
            }
        } else {
            this.yes_direction = 'bottom';
            this.no_direction = 'right';
        }

        this.yes_direction = this.yes_direction || 'bottom';
        this.no_direction = this.no_direction || 'right';

        this.text.attr({
            x: this.textMargin * 2
        });

        var width = this.text.getBBox().width + 3 * this.textMargin;
        width += width / 2;
        var height = this.text.getBBox().height + 2 * this.textMargin;
        height += height / 2;
        height = Math.max(width * 0.5, height);
        var startX = width / 4;
        var startY = height / 4;

        this.text.attr({
            x: startX + this.textMargin / 2
        });

        var start = {x: startX, y: startY};
        var points = [
            {x: startX - width / 4, y: startY + height / 4},
            {x: startX - width / 4 + width / 2, y: startY + height / 4 + height / 2},
            {x: startX - width / 4 + width, y: startY + height / 4},
            {x: startX - width / 4 + width / 2, y: startY + height / 4 - height / 2},
            {x: startX - width / 4, y: startY + height / 4}
        ];

        var symbol = drawPath(chart, start, points);

        symbol.attr({
            stroke: this.getAttr('element-color'),
            'stroke-width': this.getAttr('line-width'),
            fill: this.getAttr('fill')
        });
        if (options.link) {
            symbol.attr('href', options.link);
        }
        if (options.target) {
            symbol.attr('target', options.target);
        }
        if (options.key) {
            symbol.node.id = options.key;
        }
        symbol.node.setAttribute('class', this.getAttr('class'));

        this.text.attr({
            y: symbol.getBBox().height / 2
        });

        this.group.push(symbol);
        symbol.insertBefore(this.text);

        this.initialize();
    }

    f.inherits(Condition, Symbol);

    Condition.prototype.render = function () {

        if (this.yes_direction) {
            this[this.yes_direction + '_symbol'] = this.yes_symbol;
        }

        if (this.no_direction) {
            this[this.no_direction + '_symbol'] = this.no_symbol;
        }

        var lineLength = this.getAttr('line-length');

        if (this.bottom_symbol) {
            var bottomPoint = this.getBottom();
            var topPoint = this.bottom_symbol.getTop();

            if (!this.bottom_symbol.isPositioned) {
                this.bottom_symbol.shiftY(this.getY() + this.height + lineLength);
                this.bottom_symbol.setX(bottomPoint.x - this.bottom_symbol.width / 2);
                this.bottom_symbol.isPositioned = true;

                this.bottom_symbol.render();
            }
        }

        if (this.right_symbol) {
            var rightPoint = this.getRight();
            var leftPoint = this.right_symbol.getLeft();

            if (!this.right_symbol.isPositioned) {

                this.right_symbol.setY(rightPoint.y - this.right_symbol.height / 2);
                this.right_symbol.shiftX(this.group.getBBox().x + this.width + lineLength);

                var self = this;
                (function shift() {
                    var hasSymbolUnder = false;
                    var symb;
                    for (var i = 0, len = self.chart.symbols.length; i < len; i++) {
                        symb = self.chart.symbols[i];

                        var diff = Math.abs(symb.getCenter().x - self.right_symbol.getCenter().x);
                        if (symb.getCenter().y > self.right_symbol.getCenter().y && diff <= self.right_symbol.width / 2) {
                            hasSymbolUnder = true;
                            break;
                        }
                    }

                    if (hasSymbolUnder) {
                        self.right_symbol.setX(symb.getX() + symb.width + lineLength);
                        shift();
                    }
                })();

                this.right_symbol.isPositioned = true;

                this.right_symbol.render();
            }
        }
    };

    Condition.prototype.renderLines = function () {
        if (this.yes_symbol) {
            this.drawLineTo(this.yes_symbol, this.getAttr('yes-text'), this.yes_direction);
        }

        if (this.no_symbol) {
            this.drawLineTo(this.no_symbol, this.getAttr('no-text'), this.no_direction);
        }
    };
    function parse(input) {
        input = input || '';
        input = input.trim();

        var chart = {
            symbols: {},
            start: null,
            drawSVG: function (container, options) {
                var self = this;

                if (this.diagram) {
                    this.diagram.clean();
                }

                var diagram = new FlowChart(container, options);
                this.diagram = diagram;
                var dispSymbols = {};

                function getDisplaySymbol(s) {
                    if (dispSymbols[s.key]) {
                        return dispSymbols[s.key];
                    }

                    switch (s.symbolType) {
                        case 'start':
                            dispSymbols[s.key] = new Start(diagram, s);
                            break;
                        case 'end':
                            dispSymbols[s.key] = new End(diagram, s);
                            break;
                        case 'operation':
                            dispSymbols[s.key] = new Operation(diagram, s);
                            break;
                        case 'inputoutput':
                            dispSymbols[s.key] = new InputOutput(diagram, s);
                            break;
                        case 'subroutine':
                            dispSymbols[s.key] = new Subroutine(diagram, s);
                            break;
                        case 'condition':
                            dispSymbols[s.key] = new Condition(diagram, s);
                            break;
                        default:
                            return new Error('Wrong symbol type!');
                    }

                    return dispSymbols[s.key];
                }

                (function constructChart(s, prevDisp, prev) {
                    var dispSymb = getDisplaySymbol(s);

                    if (self.start === s) {
                        diagram.startWith(dispSymb);
                    } else if (prevDisp && prev && !prevDisp.pathOk) {
                        if (prevDisp instanceof (Condition)) {
                            if (prev.yes === s) {
                                prevDisp.yes(dispSymb);
                            }
                            if (prev.no === s) {
                                prevDisp.no(dispSymb);
                            }
                        } else {
                            prevDisp.then(dispSymb);
                        }
                    }

                    if (dispSymb.pathOk) {
                        return dispSymb;
                    }

                    if (dispSymb instanceof (Condition)) {
                        if (s.yes) {
                            constructChart(s.yes, dispSymb, s);
                        }
                        if (s.no) {
                            constructChart(s.no, dispSymb, s);
                        }
                    } else if (s.next) {
                        constructChart(s.next, dispSymb, s);
                    }

                    return dispSymb;
                })(this.start);

                diagram.render();
            },
            clean: function () {
                this.diagram.clean();
            }
        };

        var lines = [];
        var prevBreak = 0;
        for (var i0 = 1, i0len = input.length; i0 < i0len; i0++) {
            if (input[i0] === '\n' && input[i0 - 1] !== '\\') {
                var line0 = input.substring(prevBreak, i0);
                prevBreak = i0 + 1;
                lines.push(line0.replace(/\\\n/g, '\n'));
            }
        }

        if (prevBreak < input.length) {
            lines.push(input.substr(prevBreak));
        }

        for (var l = 1, len = lines.length; l < len;) {
            var currentLine = lines[l];

            if (currentLine.indexOf(': ') < 0 && currentLine.indexOf('(') < 0 && currentLine.indexOf(')') < 0 && currentLine.indexOf('->') < 0 && currentLine.indexOf('=>') < 0) {
                lines[l - 1] += '\n' + currentLine;
                lines.splice(l, 1);
                len--;
            } else {
                l++;
            }
        }

        function getSymbol(s) {
            var startIndex = s.indexOf('(') + 1;
            var endIndex = s.indexOf(')');
            if (startIndex >= 0 && endIndex >= 0) {
                return chart.symbols[s.substring(0, startIndex - 1)];
            }
            return chart.symbols[s];
        }

        function getNextPath(s) {
            var next = 'next';
            var startIndex = s.indexOf('(') + 1;
            var endIndex = s.indexOf(')');
            if (startIndex >= 0 && endIndex >= 0) {
                next = flowSymb.substring(startIndex, endIndex);
                if (next.indexOf(',') < 0) {
                    if (next !== 'yes' && next !== 'no') {
                        next = 'next, ' + next;
                    }
                }
            }
            return next;
        }

        while (lines.length > 0) {
            var line = lines.splice(0, 1)[0];

            if (line.indexOf('=>') >= 0) {
                // definition
                var parts = line.split('=>');
                var symbol = {
                    key: parts[0],
                    symbolType: parts[1],
                    text: null,
                    link: null,
                    target: null,
                    flowstate: null
                };

                var sub;

                if (symbol.symbolType.indexOf(': ') >= 0) {
                    sub = symbol.symbolType.split(': ');
                    symbol.symbolType = sub[0];
                    symbol.text = sub[1];
                }

                if (symbol.text && symbol.text.indexOf(':>') >= 0) {
                    sub = symbol.text.split(':>');
                    symbol.text = sub[0];
                    symbol.link = sub[1];
                } else if (symbol.symbolType.indexOf(':>') >= 0) {
                    sub = symbol.symbolType.split(':>');
                    symbol.symbolType = sub[0];
                    symbol.link = sub[1];
                }

                if (symbol.symbolType.indexOf('\n') >= 0) {
                    symbol.symbolType = symbol.symbolType.split('\n')[0];
                }

                /* adding support for links */
                if (symbol.link) {
                    var startIndex = symbol.link.indexOf('[') + 1;
                    var endIndex = symbol.link.indexOf(']');
                    if (startIndex >= 0 && endIndex >= 0) {
                        symbol.target = symbol.link.substring(startIndex, endIndex);
                        symbol.link = symbol.link.substring(0, startIndex - 1);
                    }
                }
                /* end of link support */

                /* adding support for flowstates */
                if (symbol.text) {
                    if (symbol.text.indexOf('|') >= 0) {
                        var txtAndState = symbol.text.split('|');
                        symbol.text = txtAndState[0];
                        symbol.flowstate = txtAndState[1].trim();
                    }
                }
                /* end of flowstate support */

                chart.symbols[symbol.key] = symbol;

            } else if (line.indexOf('->') >= 0) {
                // flow
                var flowSymbols = line.split('->');
                for (var i = 0, lenS = flowSymbols.length; i < lenS; i++) {
                    var flowSymb = flowSymbols[i];

                    var realSymb = getSymbol(flowSymb);
                    var next = getNextPath(flowSymb);

                    var direction = null;
                    if (next.indexOf(',') >= 0) {
                        var condOpt = next.split(',');
                        next = condOpt[0];
                        direction = condOpt[1].trim();
                    }

                    if (!chart.start) {
                        chart.start = realSymb;
                    }

                    if (i + 1 < lenS) {
                        var nextSymb = flowSymbols[i + 1];
                        realSymb[next] = getSymbol(nextSymb);
                        realSymb['direction_' + next] = direction;
                        direction = null;
                    }
                }
            }

        }
        return chart;
    }

    // public api interface
    flowchart.parse = parse;

})();